/*
 * Copyright 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.yangfei.SportAssistant.services;

import static com.yangfei.SportAssistant.util.Constants.RESUME_TRACK_EXTRA_NAME;
import static com.yangfei.SportAssistant.util.Constants.TAG;

import com.yangfei.SportAssistant.R;
import com.google.common.annotations.VisibleForTesting;
import com.yangfei.SportAssistant.MainTabActivity;
import com.yangfei.SportAssistant.content.DescriptionGenerator;
import com.yangfei.SportAssistant.content.DescriptionGeneratorImpl;
import com.yangfei.SportAssistant.content.Sensor;
import com.yangfei.SportAssistant.content.SportAssistantProviderUtils;
import com.yangfei.SportAssistant.content.Track;
import com.yangfei.SportAssistant.content.TrackPointsColumns;
import com.yangfei.SportAssistant.content.TracksColumns;
import com.yangfei.SportAssistant.content.Waypoint;
import com.yangfei.SportAssistant.content.WaypointCreationRequest;
import com.yangfei.SportAssistant.content.WaypointsColumns;
import com.yangfei.SportAssistant.services.tasks.PeriodicTaskExecutor;
import com.yangfei.SportAssistant.services.tasks.SplitTask;
import com.yangfei.SportAssistant.services.tasks.StatusAnnouncerFactory;
import com.yangfei.SportAssistant.stats.TripStatistics;
import com.yangfei.SportAssistant.stats.TripStatisticsBuilder;
import com.yangfei.SportAssistant.util.Constants;
import com.yangfei.SportAssistant.util.LocationUtils;
import com.yangfei.SportAssistant.util.PreferencesUtils;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ContentValues;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.hardware.SensorManager;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.Process;
import android.util.Log;

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


final class LoggingConstants
{
	public static final int LOGGING_FINE = 1;

	/**
	 * The GPS location provider is asked to update every 15 seconds or every 10
	 * meters.
	 */
	public static final int LOGGING_NORMAL = 2;

	/**
	 * The GPS location provider is asked to update every 30 seconds or every 25
	 * meters.
	 */
	public static final int LOGGING_COARSE = 3;

	/**
	 * The radio location provider is asked to update every 5 minutes or every 500
	 * meters.
	 */
	public static final int LOGGING_GLOBAL = 4;
};
/**
 * A background service that registers a location listener and records track
 * points. Track points are saved to the SportAssistantProvider.
 * 
 * @author Leif Hendrik Wilden
 */
public class TrackRecordingService extends Service implements MapServiceInterface
{

	static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3;

	private WakeLock wakeLock;

	private int minRecordingDistance = Constants.DEFAULT_MIN_RECORDING_DISTANCE;
	private int maxRecordingDistance = Constants.DEFAULT_MAX_RECORDING_DISTANCE;
	private int minRequiredAccuracy = Constants.DEFAULT_MIN_REQUIRED_ACCURACY;
	private int autoResumeTrackTimeout = Constants.DEFAULT_AUTO_RESUME_TRACK_TIMEOUT;

	private long recordingTrackId = -1;

	private long currentWaypointId = -1;

	/** The timer posts a runnable to the main thread via this handler. */
	private final Handler handler = new Handler();

	/**
	 * Utilities to deal with the database.
	 */
	private SportAssistantProviderUtils providerUtils;

	private TripStatisticsBuilder statsBuilder;
	private TripStatisticsBuilder waypointStatsBuilder;

	/**
	 * Current length of the recorded track. This length is calculated from the
	 * recorded points (as compared to each location fix). It's used to overlay
	 * waypoints precisely in the elevation profile chart.
	 */
	private double length;

	/**
	 * Status announcer executor.
	 */
	private PeriodicTaskExecutor announcementExecutor;
	private PeriodicTaskExecutor splitExecutor;

	private SensorManager sensorManager;

	private PreferenceManager prefManager;
	
	private NotificationManager notificationManager;

	/**
	 * The interval in milliseconds that we have requested to be notified of gps
	 * readings.
	 */
	private long currentRecordingInterval;

	/**
	 * The policy used to decide how often we should request gps updates.
	 */
	private LocationListenerPolicy locationListenerPolicy = new AbsoluteLocationListenerPolicy(0);

	private MapListsenerSuper mMapListsenerBaidu = null;
	private MapListsenerSuper mMapListsenerGoogle = null;
	
	private int offsetCount = 0;
	private double offsetSumLat = 0.0;
	private double offsetSumLong = 0.0;
	double offsetAvgLat = 0.0;
	double offsetAvgLong = 0.0;

	/**
	 * Task invoked by a timer periodically to make sure the location listener is
	 * still registered.
	 */
	private TimerTask checkLocationListener = new TimerTask()
	{
		@Override
		public void run()
		{
			// It's always safe to assume that if isRecording() is true, it implies
			// that onCreate() has finished.
			if (isRecording())
			{
				handler.post(new Runnable()
				{
					public void run()
					{
						Log.d(Constants.TAG, "Re-registering location listener with TrackRecordingService.");
						unregisterLocationListener();
						registerLocationListener();
					}
				});
			}
		}
	};

	/**
	 * This timer invokes periodically the checkLocationListener timer task.
	 */
	private final Timer timer = new Timer();

	/**
	 * Is the phone currently moving?
	 */
	private boolean isMoving = true;

	/**
	 * The most recent recording track.
	 */
	private Track recordingTrack;

	/**
	 * Is the service currently recording a track?
	 */
	private boolean isRecording;

	/**
	 * Last good location the service has received from the location listener
	 */
	private Location lastLocation;
	
	private Location lastLocationChina;

	/**
	 * Last valid location (i.e. not a marker) that was recorded.
	 */
	private Location lastValidLocation;

	/**
	 * A service to run tasks outside of the main thread.
	 */
	private ExecutorService executorService;

	private ServiceBinder binder = new ServiceBinder(this);

	/*
	 * Application lifetime events:
	 */

	/*
	 * Note that this service, through the AndroidManifest.xml, is configured to
	 * allow both SportAssistant and third party apps to invoke it. For the
	 * onCreate callback, we cannot tell whether the caller is SportAssistant or a
	 * third party app, thus it cannot start/stop a recording or write/update
	 * SportAssistant database. However, it can resume a recording.
	 */
	@Override
	public void onCreate()
	{
		super.onCreate();
		Log.d(TAG, "TrackRecordingService.onCreate");
		providerUtils = SportAssistantProviderUtils.Factory.get(this);
		
		mMapListsenerBaidu = MapListsenerFactory.CreateMapListsener(Constants.MapListsenerType.EBaiduMap);
		mMapListsenerBaidu.Init(this);

		mMapListsenerGoogle = MapListsenerFactory.CreateMapListsener(Constants.MapListsenerType.EGoogleMap);
		mMapListsenerGoogle.Init(this);

		//������ʾ
		setUpTaskExecutors();
		executorService = Executors.newSingleThreadExecutor();

		prefManager = new PreferenceManager(this);
		
		notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

		registerLocationListener();

		/*
		 * After 5 min, check every minute that location listener still is
		 * registered and spit out additional debugging info to the logs:
		 */
		timer.schedule(checkLocationListener, 1000 * 60, 1000 * 60);

		// Try to restore previous recording state in case this service has been
		// restarted by the system, which can sometimes happen.
		recordingTrack = getRecordingTrack();
		if (recordingTrack != null)
		{
			restoreStats(recordingTrack);
			isRecording = true;
		}
		else
		{
			if (recordingTrackId != -1L)
			{
				// Make sure we have consistent state in shared preferences.
				Log.w(TAG, "TrackRecordingService.onCreate: " + "Resetting an orphaned recording track = " + recordingTrackId);
			}
			recordingTrackId = -1L;
			PreferencesUtils.setRecordingTrackId(this, recordingTrackId);
		}
		showNotification();
	}
	
	@Override
	public void DoLocationChanged(final Location location,
			final MapListsenerSuper mapListsener)
	{
		if (executorService.isShutdown() || executorService.isTerminated())
		{
			return;
		}
		executorService.submit(new Runnable()
		{
			@Override
			public void run()
			{
				onLocationChangedAsync(location,mapListsener);
			}
		});
	}
	
	@Override
	public ContextWrapper GetContext()
	{
		return this;
	}

	/*
	 * Note that this service, through the AndroidManifest.xml, is configured to
	 * allow both SportAssistant and third party apps to invoke it. For the
	 * onStart callback, we cannot tell whether the caller is SportAssistant or a
	 * third party app, thus it cannot start/stop a recording or write/update
	 * SportAssistant database. However, it can resume a recording.
	 */
	@Override
	public void onStart(Intent intent, int startId)
	{
		handleStartCommand(intent, startId);
	}

	/*
	 * Note that this service, through the AndroidManifest.xml, is configured to
	 * allow both SportAssistant and third party apps to invoke it. For the
	 * onStartCommand callback, we cannot tell whether the caller is
	 * SportAssistant or a third party app, thus it cannot start/stop a recording
	 * or write/update SportAssistant database. However, it can resume a
	 * recording.
	 */
	@Override
	public int onStartCommand(Intent intent, int flags, int startId)
	{
		handleStartCommand(intent, startId);
		return START_STICKY;
	}

	private void handleStartCommand(Intent intent, int startId)
	{
		Log.d(TAG, "TrackRecordingService.handleStartCommand: " + startId);

		if (intent == null)
		{
			return;
		}

		// Check if called on phone reboot with resume intent.
		if (intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false))
		{
			resumeTrack(startId);
		}
	}

	private boolean isTrackInProgress()
	{
		return recordingTrackId != -1 || isRecording;
	}

	private void resumeTrack(int startId)
	{
		Log.d(TAG, "TrackRecordingService: requested resume");

		// Make sure that the current track exists and is fresh enough.
		if (recordingTrack == null || !shouldResumeTrack(recordingTrack))
		{
			Log.i(TAG, "TrackRecordingService: Not resuming, because the previous track (" + recordingTrack
					+ ") doesn't exist or is too old");
			isRecording = false;
			recordingTrackId = -1L;
			PreferencesUtils.setRecordingTrackId(this, recordingTrackId);
			stopSelfResult(startId);
			return;
		}

		Log.i(TAG, "TrackRecordingService: resuming");
	}

	@Override
	public IBinder onBind(Intent intent)
	{
		Log.d(TAG, "TrackRecordingService.onBind");
		return binder;
	}

	@Override
	public boolean onUnbind(Intent intent)
	{
		Log.d(TAG, "TrackRecordingService.onUnbind");
		return super.onUnbind(intent);
	}

	@Override
	public void onDestroy()
	{
		Log.d(TAG, "TrackRecordingService.onDestroy");

		isRecording = false;
		showNotification();
		prefManager.shutdown();
		prefManager = null;
		checkLocationListener.cancel();
		checkLocationListener = null;
		timer.cancel();
		timer.purge();
		unregisterLocationListener();
		shutdownTaskExecutors();


		// Make sure we have no indirect references to this service.

		mMapListsenerBaidu.Destroy();
		mMapListsenerGoogle.Destroy();
		
		providerUtils = null;
		binder.detachFromService();
		binder = null;

		// This should be the last operation.
		releaseWakeLock();

		// Shutdown the executor service last to avoid sending events to a dead
		// executor.
		executorService.shutdown();
		super.onDestroy();
		Log.d(TAG, "TrackRecordingService.onDestroy finish");
	}

	private void setAutoResumeTrackRetries(int retryAttempts)
	{
		Log.d(TAG, "Updating auto-resume retry attempts to: " + retryAttempts);
		prefManager.setAutoResumeTrackCurrentRetry(retryAttempts);
	}

	private boolean shouldResumeTrack(Track track)
	{
		Log.d(TAG, "shouldResumeTrack: autoResumeTrackTimeout = " + autoResumeTrackTimeout);

		// Check if we haven't exceeded the maximum number of retry attempts.
		SharedPreferences sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
		int retries = sharedPreferences.getInt(getString(R.string.auto_resume_track_current_retry_key), 0);
		Log.d(TAG, "shouldResumeTrack: Attempting to auto-resume the track (" + (retries + 1) + "/"
				+ MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS + ")");
		if (retries >= MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS)
		{
			Log.i(TAG, "shouldResumeTrack: Not resuming because exceeded the maximum " + "number of auto-resume retries");
			return false;
		}

		// Increase number of retry attempts.
		setAutoResumeTrackRetries(retries + 1);

		// Check for special cases.
		if (autoResumeTrackTimeout == 0)
		{
			// Never resume.
			Log.d(TAG, "shouldResumeTrack: Auto-resume disabled (never resume)");
			return false;
		}
		else if (autoResumeTrackTimeout == -1)
		{
			// Always resume.
			Log.d(TAG, "shouldResumeTrack: Auto-resume forced (always resume)");
			return true;
		}

		// Check if the last modified time is within the acceptable range.
		long lastModified = track.getStatistics() != null ? track.getStatistics().getStopTime() : 0;
		Log.d(TAG, "shouldResumeTrack: lastModified = " + lastModified + ", autoResumeTrackTimeout: "
				+ autoResumeTrackTimeout);
		return lastModified > 0 && System.currentTimeMillis() - lastModified <= autoResumeTrackTimeout * 60L * 1000L;
	}

	/*
	 * Setup/shutdown methods.
	 */

	/**
	 * Tries to acquire a partial wake lock if not already acquired. Logs errors
	 * and gives up trying in case the wake lock cannot be acquired.
	 */
	private void acquireWakeLock()
	{
		try
		{
			PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
			if (pm == null)
			{
				Log.e(TAG, "TrackRecordingService: Power manager not found!");
				return;
			}
			if (wakeLock == null)
			{
				wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
				if (wakeLock == null)
				{
					Log.e(TAG, "TrackRecordingService: Could not create wake lock (null).");
					return;
				}
			}
			if (!wakeLock.isHeld())
			{
				wakeLock.acquire();
				if (!wakeLock.isHeld())
				{
					Log.e(TAG, "TrackRecordingService: Could not acquire wake lock.");
				}
			}
		}
		catch (RuntimeException e)
		{
			Log.e(TAG, "TrackRecordingService: Caught unexpected exception: " + e.getMessage(), e);
		}
	}

	/**
	 * Releases the wake lock if it's currently held.
	 */
	private void releaseWakeLock()
	{
		if (wakeLock != null && wakeLock.isHeld())
		{
			wakeLock.release();
			wakeLock = null;
		}
	}

	/**
	 * Shows the notification message and icon in the notification bar.
	 */
	private void showNotification()
	{
	
		if (isRecording)
		{
		 //�����֪ͨ���ظ�OnCreate���������ʾ�쳣������
			Intent intent = new Intent(Intent.ACTION_MAIN);
			intent.addCategory(Intent.CATEGORY_LAUNCHER);
			intent.setClass(this, MainTabActivity.class);
			intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
	
			Notification notification = new Notification(R.drawable.go, null /* tickerText */, System.currentTimeMillis());
			PendingIntent contentIntent = PendingIntent.getActivity(this, 0 /* requestCode */, intent, 0 /* flags */);
			notification.setLatestEventInfo(this, getString(R.string.app_name), getString(R.string.track_record_notification), contentIntent);
			notification.flags += Notification.FLAG_NO_CLEAR;
			notificationManager.notify(1, notification);
		}
		else
		{
			notificationManager.cancelAll();
		}
	}

	@VisibleForTesting
	protected void startForegroundService(Notification notification)
	{
		startForeground(1, notification);
	}

	@VisibleForTesting
	protected void stopForegroundService()
	{
		stopForeground(true);
	}

	private void setUpTaskExecutors()
	{
		announcementExecutor = new PeriodicTaskExecutor(this, new StatusAnnouncerFactory());
		splitExecutor = new PeriodicTaskExecutor(this, new SplitTask.Factory());
	}

	private void shutdownTaskExecutors()
	{
		Log.d(TAG, "TrackRecordingService.shutdownExecuters");
		try
		{
			announcementExecutor.shutdown();
		}
		finally
		{
			announcementExecutor = null;
		}
		try
		{
			splitExecutor.shutdown();
		}
		finally
		{
			splitExecutor = null;
		}
	}

	public void registerLocationListener()
	{
		if (!mMapListsenerGoogle.IsManagerReady())
		{
			Log.e(TAG, "TrackRecordingService: Do not have any location manager.");
			return;
		}

		Log.d(TAG, "Preparing to register location listener w/ TrackRecordingService...");
		try
		{
			long desiredInterval = locationListenerPolicy.getDesiredPollingInterval();
			
			if(mMapListsenerBaidu != null)
				mMapListsenerBaidu.StartListen(desiredInterval, locationListenerPolicy.getMinDistance());
			if (mMapListsenerGoogle != null)
				mMapListsenerGoogle.StartListen(desiredInterval, locationListenerPolicy.getMinDistance());
			
			currentRecordingInterval = desiredInterval;
			Log.d(TAG, "...location listener now registered w/ TrackRecordingService @ " + currentRecordingInterval);
		}
		catch (RuntimeException e)
		{
			Log.e(TAG, "Could not register location listener: " + e.getMessage(), e);
		}
	}

	public void unregisterLocationListener()
	{
		if (!mMapListsenerGoogle.IsManagerReady())
		{
			Log.e(TAG, "TrackRecordingService: Do not have any location manager.");
			return;
		}
		
		if (mMapListsenerBaidu != null)
			mMapListsenerBaidu.StopListen();
		if (mMapListsenerGoogle != null)
			mMapListsenerGoogle.StopListen();
		Log.d(TAG, "Location listener now unregistered w/ TrackRecordingService.");
	}

	private String getDefaultActivityType(Context context)
	{
		SharedPreferences prefs = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
		return prefs.getString(context.getString(R.string.default_activity_key), "");
	}

	/*
	 * Recording lifecycle.
	 */

	private long startNewTrack()
	{
		Log.d(TAG, "TrackRecordingService.startNewTrack");
		if (isTrackInProgress())
		{
			return -1L;
		}

		long startTime = System.currentTimeMillis();
		acquireWakeLock();

		Track track = new Track();
		TripStatistics trackStats = track.getStatistics();
		trackStats.setStartTime(startTime);
		track.setStartId(-1);
		Uri trackUri = providerUtils.insertTrack(track);
		recordingTrackId = Long.parseLong(trackUri.getLastPathSegment());
		track.setId(recordingTrackId);
		track.setName(new DefaultTrackNameFactory(this).getDefaultTrackName(recordingTrackId, startTime));
		track.setCategory(getDefaultActivityType(this));
		isRecording = true;
		isMoving = true;
		
		mMapListsenerBaidu.setMoving(true);
		mMapListsenerGoogle.setMoving(true);

		providerUtils.updateTrack(track);
		statsBuilder = new TripStatisticsBuilder(startTime);
		statsBuilder.setMinRecordingDistance(minRecordingDistance);
		
		waypointStatsBuilder = new TripStatisticsBuilder(startTime);
		waypointStatsBuilder.setMinRecordingDistance(minRecordingDistance);
		currentWaypointId = insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
		length = 0;
		showNotification();
		registerLocationListener();

		// Reset the number of auto-resume retries.
		setAutoResumeTrackRetries(0);
		// Persist the current recording track.
		PreferencesUtils.setRecordingTrackId(this, recordingTrackId);

		// Notify the world that we're now recording.
		sendTrackBroadcast(R.string.track_started_broadcast_action, recordingTrackId);
		announcementExecutor.restore();
		splitExecutor.restore();

		return recordingTrackId;
	}

	private void restoreStats(Track track)
	{
		Log.d(TAG, "Restoring stats of track with ID: " + track.getId());

		TripStatistics stats = track.getStatistics();
		statsBuilder = new TripStatisticsBuilder(stats.getStartTime());
		statsBuilder.setMinRecordingDistance(minRecordingDistance);
		
		length = 0;
		lastValidLocation = null;

		Waypoint waypoint = providerUtils.getFirstWaypoint(recordingTrackId);
		if (waypoint != null && waypoint.getStatistics() != null)
		{
			currentWaypointId = waypoint.getId();
			waypointStatsBuilder = new TripStatisticsBuilder(waypoint.getStatistics());
		}
		else
		{
			// This should never happen, but we got to do something so life goes on:
			waypointStatsBuilder = new TripStatisticsBuilder(stats.getStartTime());
			currentWaypointId = -1;
		}
		waypointStatsBuilder.setMinRecordingDistance(minRecordingDistance);

		Cursor cursor = null;
		try
		{
			cursor = providerUtils.getLocationsCursor(recordingTrackId, -1, Constants.MAX_LOADED_TRACK_POINTS, true);
			if (cursor != null)
			{
				if (cursor.moveToLast())
				{
					do
					{
						Location location = providerUtils.createLocation(cursor);
						if (LocationUtils.isValidLocation(location))
						{
							statsBuilder.addLocation(location, location.getTime());
							if (lastValidLocation != null)
							{
								length += location.distanceTo(lastValidLocation);
							}
							lastValidLocation = location;
						}
					}
					while (cursor.moveToPrevious());
				}
				statsBuilder.getStatistics().setMovingTime(stats.getMovingTime());
				statsBuilder.pauseAt(stats.getStopTime());
				statsBuilder.resumeAt(System.currentTimeMillis());
			}
			else
			{
				Log.e(TAG, "Could not get track points cursor.");
			}
		}
		catch (RuntimeException e)
		{
			Log.e(TAG, "Error while restoring track.", e);
		}
		finally
		{
			if (cursor != null)
			{
				cursor.close();
			}
		}

		announcementExecutor.restore();
		splitExecutor.restore();
	}
	
	private void getChinaOffset(Location googleLocation,Location chinaLocation)
	{
		if(googleLocation == null || chinaLocation == null)
			return;
		
		//�õ���ʼƫ���Ժ����ȡ���gps����
		double gpsFault = 0;
		if( offsetAvgLat == 0 || offsetAvgLong == 0 )
			gpsFault = 20;
		else
			gpsFault = 10;
		
		if( googleLocation.getAccuracy() > gpsFault || chinaLocation.getAccuracy() > gpsFault )
		{
			return;
		}
		
		double offsetLongitude = chinaLocation.getLongitude() - googleLocation.getLongitude();
		double offsetLatitude = chinaLocation.getLatitude() - googleLocation.getLatitude();
		
		offsetSumLat += offsetLatitude;
		offsetSumLong += offsetLongitude;
		
		offsetCount++;
		
		offsetAvgLat = offsetSumLat/offsetCount;
		offsetAvgLong = offsetSumLong/offsetCount;
		
		
	}

	private void onLocationChangedAsync(Location location,MapListsenerSuper mapListsener)
	{
		Log.d(TAG, "TrackRecordingService.onLocationChanged");
		
		// This should never happen, but just in case (we really don't want the
		// service to crash):
		if (location == null)
		{
			Log.w(TAG, "Location changed, but location is null.");
			return;
		}
		
		Log.w("CoordinateUpdate", "onLocationChanged" + "Latitude = "
				+ location.getLatitude() + "Longitude = " + location.getLongitude());
		Log.w("CoordinateUpdate", "onLocationChanged" + mapListsener.GetMapProviderName());
		
		// Don't record if the accuracy is too bad:
		if (location.getAccuracy() > minRequiredAccuracy /*|| location.getAccuracy() == 0.0*/)
		{
			Log.d(TAG, "Not recording. Bad accuracy.");
			return;
		}
		
		if (LocationUtils.isValidLocation(location) == false)
		{
			Log.d(TAG, "Not recording. Bad location.");
			return;
		}
			
		if(mapListsener.GetMapProviderName() == Constants.BaiduMap)
		{
			lastLocationChina = location;
			//getChinaOffset(lastValidLocation,location);
			return;
		}
		
		try
		{
			// Don't record if the service has been asked to pause recording:
			if (!isRecording)
			{
				Log.w(TAG, "Not recording because recording has been paused.");
				return;
			}
			else
			{
				// At least one track must be available for appending points:
				recordingTrack = getRecordingTrack();
				if (recordingTrack == null)
				{
					Log.d(TAG, "Not recording. No track to append to available.");
					return;
				}
			}

			// Update the idle time if needed.
			locationListenerPolicy.updateIdleTime(statsBuilder.getIdleTime());
			addLocationToStats(location);
			if (currentRecordingInterval != locationListenerPolicy.getDesiredPollingInterval())
			{
				registerLocationListener();
			}

			Location lastRecordedLocation = providerUtils.getLastLocation();
			double distanceToLastRecorded = Double.POSITIVE_INFINITY;
			if (lastRecordedLocation != null)
			{
				distanceToLastRecorded = location.distanceTo(lastRecordedLocation);
			}
			double distanceToLast = Double.POSITIVE_INFINITY;
			if (lastLocation != null)
			{
				distanceToLast = location.distanceTo(lastLocation);
			}

			// If the user has been stationary for two recording just record the first
			// two and ignore the rest. This code will only have an effect if the
			// maxRecordingDistance = 0
			if (distanceToLast == 0)
			{
				if (isMoving)
				{
					Log.d(TAG, "Found two identical locations.");
					isMoving = false;
					if (lastLocation != null && lastRecordedLocation != null && !lastRecordedLocation.equals(lastLocation))
					{
						// Need to write the last location. This will happen when
						// lastRecordedLocation.distance(lastLocation) <
						// minRecordingDistance
						if (!insertLocation(lastLocation, lastRecordedLocation, recordingTrackId))
						{
							return;
						}
					}
				}
				else
				{
					Log.d(TAG, "Not recording. More than two identical locations.");
				}
			}
			else if (distanceToLastRecorded > minRecordingDistance)
			{
				if (lastLocation != null && !isMoving)
				{
					// Last location was the last stationary location. Need to go back and
					// add it.
					if (!insertLocation(lastLocation, lastRecordedLocation, recordingTrackId))
					{
						return;
					}
					isMoving = true;
				}

				// If separation from last recorded point is too large insert a
				// separator to indicate end of a segment:
				boolean startNewSegment =
						lastRecordedLocation != null && lastRecordedLocation.getLatitude() < 90 &&
						distanceToLastRecorded > maxRecordingDistance &&
						recordingTrack.getStartId() >= 0;
				if (startNewSegment)
				{
					// Insert a separator point to indicate start of new track:
					Log.d(TAG, "Inserting a separator.");
					Location separator = new Location(LocationManager.GPS_PROVIDER);
					separator.setLongitude(0);
					separator.setLatitude(100);
					//for debug
					///////////////////////////
		      Bundle chinaCoord = new Bundle();
		      chinaCoord.putDouble(TrackPointsColumns.LATITUDECHINA, distanceToLastRecorded);
		      chinaCoord.putDouble(TrackPointsColumns.LONGITUDECHINA, lastRecordedLocation.getLongitude());
		      separator.setExtras(chinaCoord); 
					///////////////////////////
					separator.setTime(lastRecordedLocation.getTime());
					providerUtils.insertTrackPoint(separator, recordingTrackId);
				}

				if (!insertLocation(location, lastRecordedLocation, recordingTrackId))
				{
					return;
				}
			}
			else
			{
				Log.d(TAG, String.format("Not recording. Distance to last recorded point (%f m) is less than" + " %d m.", distanceToLastRecorded, minRecordingDistance));
				// Return here so that the location is NOT recorded as the last
				// location.
				return;
			}
		}
		catch (Error e)
		{
			// Probably important enough to rethrow.
			Log.e(TAG, "Error in onLocationChanged", e);
			throw e;
		}
		catch (RuntimeException e)
		{
			// Safe usually to trap exceptions.
			Log.e(TAG, "Trapping exception in onLocationChanged", e);
			throw e;
		}
		lastLocation = location;
	}

	/**
	 * Inserts a new location in the track points db and updates the corresponding
	 * track in the track db.
	 * 
	 * @param location
	 *          the location to be inserted
	 * @param lastRecordedLocation
	 *          the last recorded location before this one (or null if none)
	 * @param trackId
	 *          the id of the track
	 * @return true if successful. False if SQLite3 threw an exception.
	 */
	private boolean insertLocation(Location location, Location lastRecordedLocation, long trackId)
	{

		Log.d("CoordinateUpdate", "insertLocation");
		// Keep track of length along recorded track (needed when a waypoint is
		// inserted):
		if (LocationUtils.isValidLocation(location))
		{
			if (lastValidLocation != null)
			{
				length += location.distanceTo(lastValidLocation);
			}
			lastValidLocation = location;
		}

		// Insert the new location:
		try
		{
			Location locationToInsert = location;
			
			//for china offset
			if( lastLocationChina != null)
			{
				if (!LocationUtils.isValidLocation(lastLocationChina))
				{
					Log.w(TAG, "lastLocationChina is InValid++++++++++++++++++++++++++++");
				}
				else
				{
		      Bundle chinaCoord = new Bundle();
		      chinaCoord.putDouble(TrackPointsColumns.LATITUDECHINA, lastLocationChina.getLatitude());
		      chinaCoord.putDouble(TrackPointsColumns.LONGITUDECHINA, lastLocationChina.getLongitude());
		      locationToInsert.setExtras(chinaCoord); 
				}
			}
			
			Uri pointUri = providerUtils.insertTrackPoint(locationToInsert, trackId);
			int pointId = Integer.parseInt(pointUri.getLastPathSegment());

			// Update the current track:
			if (lastRecordedLocation != null && lastRecordedLocation.getLatitude() < 90)
			{
				ContentValues values = new ContentValues();
				TripStatistics stats = statsBuilder.getStatistics();
				if (recordingTrack.getStartId() < 0)
				{
					values.put(TracksColumns.STARTID, pointId);
					recordingTrack.setStartId(pointId);
				}
				values.put(TracksColumns.STOPID, pointId);
				values.put(TracksColumns.STOPTIME, System.currentTimeMillis());
				values.put(TracksColumns.NUMPOINTS, recordingTrack.getNumberOfPoints() + 1);
				values.put(TracksColumns.MINLAT, stats.getBottom());
				values.put(TracksColumns.MAXLAT, stats.getTop());
				values.put(TracksColumns.MINLON, stats.getLeft());
				values.put(TracksColumns.MAXLON, stats.getRight());
				values.put(TracksColumns.TOTALDISTANCE, stats.getTotalDistance());
				values.put(TracksColumns.TOTALTIME, stats.getTotalTime());
				values.put(TracksColumns.MOVINGTIME, stats.getMovingTime());
				values.put(TracksColumns.AVGSPEED, stats.getAverageSpeed());
				values.put(TracksColumns.AVGMOVINGSPEED, stats.getAverageMovingSpeed());
				values.put(TracksColumns.MAXSPEED, stats.getMaxSpeed());
				values.put(TracksColumns.MINELEVATION, stats.getMinElevation());
				values.put(TracksColumns.MAXELEVATION, stats.getMaxElevation());
				values.put(TracksColumns.ELEVATIONGAIN, stats.getTotalElevationGain());
				values.put(TracksColumns.MINGRADE, stats.getMinGrade());
				values.put(TracksColumns.MAXGRADE, stats.getMaxGrade());
				values.put(TracksColumns.ChinaOffsetLat, offsetAvgLat);
				values.put(TracksColumns.ChinaOffsetLong, offsetAvgLong);
				getContentResolver().update(TracksColumns.CONTENT_URI, values, "_id=" + recordingTrack.getId(), null);
				updateCurrentWaypoint();
			}
		}
		catch (SQLiteException e)
		{
			// Insert failed, most likely because of SqlLite error code 5
			// (SQLite_BUSY). This is expected to happen extremely rarely (if our
			// listener gets invoked twice at about the same time).
			Log.w(TAG, "Caught SQLiteException: " + e.getMessage(), e);
			return false;
		}
		//������ʾ
		announcementExecutor.update();
		splitExecutor.update();
		return true;
	}

	private void updateCurrentWaypoint()
	{
		if (currentWaypointId >= 0)
		{
			ContentValues values = new ContentValues();
			TripStatistics waypointStats = waypointStatsBuilder.getStatistics();
			values.put(WaypointsColumns.STARTTIME, waypointStats.getStartTime());
			values.put(WaypointsColumns.LENGTH, length);
			values.put(WaypointsColumns.DURATION, System.currentTimeMillis() - statsBuilder.getStatistics().getStartTime());
			values.put(WaypointsColumns.TOTALDISTANCE, waypointStats.getTotalDistance());
			values.put(WaypointsColumns.TOTALTIME, waypointStats.getTotalTime());
			values.put(WaypointsColumns.MOVINGTIME, waypointStats.getMovingTime());
			values.put(WaypointsColumns.AVGSPEED, waypointStats.getAverageSpeed());
			values.put(WaypointsColumns.AVGMOVINGSPEED, waypointStats.getAverageMovingSpeed());
			values.put(WaypointsColumns.MAXSPEED, waypointStats.getMaxSpeed());
			values.put(WaypointsColumns.MINELEVATION, waypointStats.getMinElevation());
			values.put(WaypointsColumns.MAXELEVATION, waypointStats.getMaxElevation());
			values.put(WaypointsColumns.ELEVATIONGAIN, waypointStats.getTotalElevationGain());
			values.put(WaypointsColumns.MINGRADE, waypointStats.getMinGrade());
			values.put(WaypointsColumns.MAXGRADE, waypointStats.getMaxGrade());
			getContentResolver().update(WaypointsColumns.CONTENT_URI, values, "_id=" + currentWaypointId, null);
		}
	}

	private void addLocationToStats(Location location)
	{
		if (LocationUtils.isValidLocation(location))
		{
			long now = System.currentTimeMillis();
			statsBuilder.addLocation(location, now);
			waypointStatsBuilder.addLocation(location, now);
		}
	}

	/*
	 * Application lifetime events: ============================
	 */

	public long insertWaypoint(WaypointCreationRequest request)
	{
		if (!isRecording())
		{
			throw new IllegalStateException("Unable to insert waypoint marker while not recording!");
		}
		if (request == null)
		{
			request = WaypointCreationRequest.DEFAULT_MARKER;
		}
		Waypoint wpt = new Waypoint();
		switch (request.getType())
		{
			case MARKER:
				buildMarker(wpt, request);
				break;
			case STATISTICS:
				buildStatisticsMarker(wpt);
				break;
		}
		wpt.setTrackId(recordingTrackId);
		wpt.setLength(length);
		if (lastLocation == null || statsBuilder == null || statsBuilder.getStatistics() == null)
		{
			// A null location is ok, and expected on track start.
			// Make it an impossible location.
			Location l = new Location("");
			l.setLatitude(100);
			l.setLongitude(180);
			wpt.setLocation(l);
		}
		else
		{
			wpt.setLocation(lastLocation);
			wpt.setDuration(lastLocation.getTime() - statsBuilder.getStatistics().getStartTime());
		}
		Uri uri = providerUtils.insertWaypoint(wpt);
		return Long.parseLong(uri.getLastPathSegment());
	}

	private void buildMarker(Waypoint wpt, WaypointCreationRequest request)
	{
		wpt.setType(Waypoint.TYPE_WAYPOINT);
		if (request.getIconUrl() == null)
		{
			wpt.setIcon(getString(R.string.marker_waypoint_icon_url));
		}
		else
		{
			wpt.setIcon(request.getIconUrl());
		}
		if (request.getName() == null)
		{
			wpt.setName(getString(R.string.marker_type_waypoint));
		}
		else
		{
			wpt.setName(request.getName());
		}
		if (request.getDescription() != null)
		{
			wpt.setDescription(request.getDescription());
		}
	}

	/**
	 * Build a statistics marker. A statistics marker holds the stats for the*
	 * last segment up to this marker.
	 * 
	 * @param waypoint
	 *          The waypoint which will be populated with stats data.
	 */
	private void buildStatisticsMarker(Waypoint waypoint)
	{
		DescriptionGenerator descriptionGenerator = new DescriptionGeneratorImpl(this);

		// Set stop and total time in the stats data
		final long time = System.currentTimeMillis();
		waypointStatsBuilder.pauseAt(time);

		// Override the duration - it's not the duration from the last waypoint, but
		// the duration from the beginning of the whole track
		waypoint.setDuration(time - statsBuilder.getStatistics().getStartTime());

		// Set the rest of the waypoint data
		waypoint.setType(Waypoint.TYPE_STATISTICS);
		waypoint.setName(getString(R.string.marker_type_statistics));
		waypoint.setStatistics(waypointStatsBuilder.getStatistics());
		waypoint.setDescription(descriptionGenerator.generateWaypointDescription(waypoint));
		waypoint.setIcon(getString(R.string.marker_statistics_icon_url));

		waypoint.setStartId(providerUtils.getLastLocationId(recordingTrackId));

		// Create a new stats keeper for the next marker.
		waypointStatsBuilder = new TripStatisticsBuilder(time);
	}

	private void endCurrentTrack()
	{
		Log.d(TAG, "TrackRecordingService.endCurrentTrack");
		if (!isTrackInProgress())
		{
			return;
		}

		announcementExecutor.shutdown();
		splitExecutor.shutdown();
		isRecording = false;
		Track recordedTrack = providerUtils.getTrack(recordingTrackId);
		if (recordedTrack != null)
		{
			TripStatistics stats = recordedTrack.getStatistics();
			stats.setStopTime(System.currentTimeMillis());
			stats.setTotalTime(stats.getStopTime() - stats.getStartTime());
			long lastRecordedLocationId = providerUtils.getLastLocationId(recordingTrackId);
			ContentValues values = new ContentValues();
			if (lastRecordedLocationId >= 0 && recordedTrack.getStopId() >= 0)
			{
				values.put(TracksColumns.STOPID, lastRecordedLocationId);
			}
			values.put(TracksColumns.STOPTIME, stats.getStopTime());
			values.put(TracksColumns.TOTALTIME, stats.getTotalTime());
			getContentResolver().update(TracksColumns.CONTENT_URI, values, "_id=" + recordedTrack.getId(), null);
		}
		showNotification();
		long recordedTrackId = recordingTrackId;
		recordingTrackId = -1L;
		PreferencesUtils.setRecordingTrackId(this, recordingTrackId);

		releaseWakeLock();

		// Notify the world that we're no longer recording.
		sendTrackBroadcast(R.string.track_stopped_broadcast_action, recordedTrackId);

		stopSelf();
	}

	private void sendTrackBroadcast(int actionResId, long trackId)
	{
		Intent broadcastIntent = new Intent().setAction(getString(actionResId)).putExtra(getString(R.string.track_id_broadcast_extra), trackId);
		sendBroadcast(broadcastIntent, getString(R.string.permission_notification_value));

		SharedPreferences sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
		if (sharedPreferences.getBoolean(getString(R.string.allow_access_key), false))
		{
			sendBroadcast(broadcastIntent, getString(R.string.broadcast_notifications_permission));
		}
	}

	/*
	 * Data/state access.
	 */

	private Track getRecordingTrack()
	{
		if (recordingTrackId < 0)
		{
			return null;
		}

		return providerUtils.getTrack(recordingTrackId);
	}

	public boolean isRecording()
	{
		return isRecording;
	}

	public TripStatistics getTripStatistics()
	{
		return statsBuilder.getStatistics();
	}

	Location getLastLocation()
	{
		return lastLocation;
	}

	long getRecordingTrackId()
	{
		return recordingTrackId;
	}

	void setRecordingTrackId(long recordingTrackId)
	{
		this.recordingTrackId = recordingTrackId;
	}

	void setMaxRecordingDistance(int maxRecordingDistance)
	{
		this.maxRecordingDistance = maxRecordingDistance;
	}

	void setMinRecordingDistance(int minRecordingDistance)
	{
		this.minRecordingDistance = minRecordingDistance;
		if (statsBuilder != null && waypointStatsBuilder != null)
		{
			statsBuilder.setMinRecordingDistance(minRecordingDistance);
			waypointStatsBuilder.setMinRecordingDistance(minRecordingDistance);
		}
	}

	void setMinRequiredAccuracy(int minRequiredAccuracy)
	{
		this.minRequiredAccuracy = minRequiredAccuracy;
	}

	void setLocationListenerPolicy(LocationListenerPolicy locationListenerPolicy)
	{
		this.locationListenerPolicy = locationListenerPolicy;
	}

	void setAutoResumeTrackTimeout(int autoResumeTrackTimeout)
	{
		this.autoResumeTrackTimeout = autoResumeTrackTimeout;
	}

	void setAnnouncementFrequency(int announcementFrequency)
	{
		announcementExecutor.setTaskFrequency(announcementFrequency);
	}

	void setSplitFrequency(int frequency)
	{
		splitExecutor.setTaskFrequency(frequency);
	}

	void setMetricUnits(boolean metric)
	{
		announcementExecutor.setMetricUnits(metric);
		splitExecutor.setMetricUnits(metric);
	}

	/**
	 * TODO: There is a bug in Android that leaks Binder instances. This bug is
	 * especially visible if we have a non-static class, as there is no way to
	 * nullify reference to the outer class (the service). A workaround is to use
	 * a static class and explicitly clear service and detach it from the
	 * underlying Binder. With this approach, we minimize the leak to 24 bytes per
	 * each service instance.
	 * 
	 * For more details, see the following bug:
	 * http://code.google.com/p/android/issues/detail?id=6426.
	 */
	private static class ServiceBinder extends ITrackRecordingService.Stub
	{
		private TrackRecordingService service;
		private DeathRecipient deathRecipient;

		public ServiceBinder(TrackRecordingService service)
		{
			this.service = service;
		}

		// Logic for letting the actual service go up and down.

		@Override
		public boolean isBinderAlive()
		{
			// Pretend dead if the service went down.
			return service != null;
		}

		@Override
		public boolean pingBinder()
		{
			return isBinderAlive();
		}

		@Override
		public void linkToDeath(DeathRecipient recipient, int flags)
		{
			deathRecipient = recipient;
		}

		@Override
		public boolean unlinkToDeath(DeathRecipient recipient, int flags)
		{
			if (!isBinderAlive())
			{
				return false;
			}

			deathRecipient = null;
			return true;
		}

		/**
		 * Clears the reference to the outer class to minimize the leak.
		 */
		private void detachFromService()
		{
			this.service = null;
			attachInterface(null, null);

			if (deathRecipient != null)
			{
				deathRecipient.binderDied();
			}
		}

		/**
		 * Checks if the service is available. If not, throws an
		 * {@link IllegalStateException}.
		 */
		private void checkService()
		{
			if (service == null)
			{
				throw new IllegalStateException("The service has been already detached!");
			}
		}

		/**
		 * Returns true if the RPC caller is from the same application or if the
		 * "Allow access" setting indicates that another app can invoke this
		 * service's RPCs.
		 */
		private boolean canAccess()
		{

			// As a precondition for access, must check if the service is available.
			checkService();

			if (Process.myPid() == Binder.getCallingPid())
			{
				return true;
			}
			else
			{
				SharedPreferences sharedPreferences = service.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
				return sharedPreferences.getBoolean(service.getString(R.string.allow_access_key), false);
			}
		}

		// Service method delegates.

		@Override
		public boolean isRecording()
		{
			if (!canAccess())
			{
				return false;
			}
			return service.isRecording();
		}

		@Override
		public long getRecordingTrackId()
		{
			if (!canAccess())
			{
				return -1L;
			}
			return service.recordingTrackId;
		}

		@Override
		public long startNewTrack()
		{
			if (!canAccess())
			{
				return -1L;
			}
			return service.startNewTrack();
		}

		/**
		 * Inserts a waypoint marker in the track being recorded.
		 * 
		 * @param request
		 *          Details of the waypoint to insert
		 * @return the unique ID of the inserted marker
		 */
		public long insertWaypoint(WaypointCreationRequest request)
		{
			if (!canAccess())
			{
				return -1L;
			}
			return service.insertWaypoint(request);
		}

		@Override
		public void endCurrentTrack()
		{
			if (!canAccess())
			{
				return;
			}
			service.endCurrentTrack();
		}

		@Override
		public void recordLocation(Location loc)
		{
			if (!canAccess())
			{
				return;
			}
			//do not know the use of this func,so just give it a null
			service.DoLocationChanged(loc,null);
		}

		@Override
		public byte[] getSensorData()
		{
			return null;
		}

		@Override
		public int getSensorState()
		{
			return Sensor.SensorState.NONE.getNumber();
		}
	}

}
